package jeme.math;

import java.math.BigDecimal;
import java.math.BigInteger;

/**
 * Represents an integer number in Scheme.
 * 
 * @author Erik Silkensen (silkense@colorado.edu)
 * @version Jul 12, 2009
 */
public class SchemeInteger extends SchemeRational 
{
    /** The <code>SchemeInteger</code> constant zero. */
    public static final SchemeInteger ZERO = new SchemeInteger(BigInteger.ZERO);

    /** The <code>SchemeInteger</code> constant one. */
    public static final SchemeInteger ONE = new SchemeInteger(BigInteger.ONE);

    /** The <code>SchemeInteger</code> constant ten. */
    public static final SchemeInteger TEN = new SchemeInteger(BigInteger.TEN);
    
    private BigInteger value;

    private boolean exact;

    /**
     * Creates an exact integer of the specified radix by parsing the specified 
     * value.
     * 
     * @param value  the string representation of this integer value
     * @param radix  the radix of this integer value
     */
    public SchemeInteger(String value, int radix) 
    {
        this(value, radix, true);
    }
    
    /**
     * Creates an exact integer of the specified value.
     * 
     * @param value  the value of this integer
     */
    public SchemeInteger(BigInteger value) 
    {
        this(value, true);
    }

    /**
     * Creates an integer of the specified radix and exactness by parsing the
     * specified value. 
     * 
     * @param value  the string representation of this integer value
     * @param radix  the radix of this integer value
     * @param exact  the exactness of this integer value
     */
    public SchemeInteger(String value, int radix, boolean exact) 
    {
        this(new BigInteger(value, radix), exact);
    }

    /**
     * Creates an integer of the specified value and exactness.
     * 
     * @param value  the value of this integer
     * @param exact  the exactness of this integer value
     */
    public SchemeInteger(BigInteger value, boolean exact) 
    {
        setValue(value);
        setExact(exact);
        setNumerator(this);
        setRealPart(this);
    }
    
    /**
     * Returns whether or not this number is an integer.
     * 
     * @return  <code>true</code>
     */
    public boolean isInteger() 
    {
        return true;
    }

    /**
     * Returns the value of this integer.
     * 
     * @return  the value of this integer
     */
    public BigInteger getValue() 
    {
        return value;
    }

    /**
     * Sets the value of this integer.
     * 
     * @param value  the value of this integer
     */
    protected void setValue(BigInteger value) 
    {
        this.value = value;
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#isZero()
     */
    public boolean isZero() 
    {
        return getValue().compareTo(BigInteger.ZERO) == 0;
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#isOne()
     */
    public boolean isOne() 
    {
        return getValue().compareTo(BigInteger.ONE) == 0;
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#isExact()
     */
    public boolean isExact() 
    {
        return exact;
    }

    /**
     * Sets the exactness of this integer value.
     * 
     * @param exact  the exactness of this integer value
     */
    public void setExact(boolean exact) 
    {
        this.exact = exact;
    }

    /**
     * Returns a new <code>SchemeInteger</code> that is the negation of this
     * number.
     * 
     * @return  the negation of this number
     */
    public SchemeNumber negate() 
    {
        return new SchemeInteger(getValue().negate());
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#toString()
     */
    public String toString() 
    {
        return getValue().toString() + (isExact() ? "" : ".");
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#add(jeme.math.SchemeNumber)
     */
    public SchemeNumber add(SchemeNumber augend) 
    {
        if (augend instanceof SchemeInteger) {
            SchemeInteger integer = (SchemeInteger) augend;
            boolean exact = isExact() && integer.isExact();
            BigInteger value = getValue().add(integer.getValue());
            return new SchemeInteger(value, exact);
        } else if (augend instanceof SchemeNumber) {
            return augend.add(this);
        } else {
            throw new IllegalArgumentException("augend must be number");
        }
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#subtract(jeme.math.SchemeNumber)
     */
    public SchemeNumber subtract(SchemeNumber subtrahend) 
    {
        if (subtrahend instanceof SchemeInteger) {
            SchemeInteger integer = (SchemeInteger) subtrahend;
            boolean exact = isExact() && integer.isExact();
            BigInteger value = getValue().subtract(integer.getValue());

            return new SchemeInteger(value, exact);
        } else if (subtrahend instanceof SchemeRational) {
            return super.subtract(subtrahend);
        } else if (subtrahend instanceof SchemeReal) {
            return toReal().subtract(subtrahend);
        } else if (subtrahend instanceof SchemeComplex) {
            return toComplex().subtract(subtrahend);
        } else {
            throw new IllegalArgumentException("subtrahend must be integer");
        }
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#multiply(jeme.math.SchemeNumber)
     */
    public SchemeNumber multiply(SchemeNumber multiplicand) 
    {
        if (multiplicand instanceof SchemeInteger) {
            SchemeInteger integer = (SchemeInteger) multiplicand;
            boolean exact = isExact() && integer.isExact();
            BigInteger value = getValue().multiply(integer.getValue());

            return new SchemeInteger(value, exact);
        } else if (multiplicand instanceof SchemeRational) {
            return super.multiply(multiplicand);
        } else if (multiplicand instanceof SchemeReal) {
            return toReal().multiply(multiplicand);
        } else if (multiplicand instanceof SchemeComplex) {
            return toComplex().multiply(multiplicand);
        } else {
            throw new IllegalArgumentException("multiplicand must be integer");
        }
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#divide(jeme.math.SchemeNumber)
     */
    public SchemeNumber divide(SchemeNumber divisor) 
    {
        if (divisor instanceof SchemeInteger) {
            SchemeInteger integer = (SchemeInteger) divisor;
            boolean exact = isExact() && integer.isExact();
            
            if (exact) {
                SchemeInteger num = new SchemeInteger(this.getValue());
                SchemeInteger den = new SchemeInteger(integer.getValue());
                return new SchemeReal(num, den);
            } else {
                BigDecimal decimal = new BigDecimal(integer.getValue());
                BigDecimal value = new BigDecimal(getValue()).divide(decimal);
                return new SchemeReal(value);
            }
        } else if (divisor instanceof SchemeRational) {
            return super.divide(divisor);
        } else if (divisor instanceof SchemeReal) {
            return toReal().divide(divisor);
        } else if (divisor instanceof SchemeComplex) {
            return toComplex().divide(divisor);
        } else {
            throw new IllegalArgumentException("divisor must be integer");
        }
    }

    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#toComplex()
     */
    public SchemeComplex toComplex() 
    {
        return new SchemeComplex(this, SchemeInteger.ZERO);
    }

    /**
     * Returns a new <code>SchemeInteger</code> version of this number.
     * 
     * @return  a new <code>SchemeInteger</code> version of this number
     */
    public SchemeReal toReal() 
    {
        return new SchemeReal(this, SchemeInteger.ONE);
    }

    /**
     * Returns a new <code>SchemeRational</code> version of this number.
     * 
     * @return  a new <code>SchemeRational</code> verison of this number
     */
    public SchemeRational toRational() 
    {
        return new SchemeRational(this, SchemeInteger.ONE);
    }

    /**
     * Returns an integer version of this number.
     * 
     * @return  <code>this</code>
     */
    public SchemeInteger toInteger() 
    {
        return this;
    }
    
    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#equals(java.lang.Object)
     */
    public boolean equals(Object other)
    {
        if (other instanceof SchemeInteger) {
            return getValue().equals(((SchemeInteger) other).getValue());
        } else if (other instanceof SchemeRational) {
            return super.equals(other);
        } else if (other instanceof SchemeReal) {
            return toReal().equals(other);
        } else if (other instanceof SchemeComplex) {
            return toComplex().equals(other);
        }
        
        return false;
    }
    
    /*
     * (non-Javadoc)
     * @see jeme.math.SchemeReal#compareTo(jeme.math.SchemeNumber)
     */
    public int compareTo(SchemeNumber other) 
    {
        if (other instanceof SchemeInteger) {
            return getValue().compareTo(((SchemeInteger) other).getValue());
        } else if (other instanceof SchemeRational) {
            return super.compareTo(other);
        } else if (other instanceof SchemeReal) {
            return toReal().compareTo(other);
        } else {
            throw new IllegalArgumentException("other must be real");
        }
    }
}
